<?php
/**
 * 框架 - 简单的[ActiveRecord]模式
 * 
 * @version $Id: ActiveRecord.php 210 2011-07-13 09:44:29Z linsir123 $
 */
class ActiveRecord
{
	/**
	 * @var 初始化结构，按[Select]语句的排列顺序
	 */
	private static $__selectPartsInit = array(
		'AGGREGATION' => '',
		'DISTINCT' => false,
		'FIELD' => array(),
		'FROM' 	=> array(),
		'WHERE' => '',
		'GROUP' => '',
		'ORDER' => array(), 
		'LIMIT' => array()
	);
	
	/**
	 * @var 数据源
	 */
	private $_dbo;
	
	/**
	 * 分表时是否已检查表的存在性
	 */
	public $_checkTable = false;
	
	/**
	 * @var 当前操作的SQL
	 */
	public $query = null;
	
	/**
	 * @var 模型SQL操作警告集合
	 */
	public $errors = array();
	
	/**
	 * @var `数据库表名`
	 */
	public $safeTable;
	
	/**
	 * @var 数据操作或查询使用的条件对象
	 */
	public $where;
	
	/**
	 * @var 查询结构
	 */
	public $parts;
	
	
	//// 以下属性需要在添加子类时进行赋值 ////
	
	
	/**
	 * @var 数据库表名（没有前后缀，由系统自动添加）
	 */
	public $table = null;
	
	/**
	 * 连接数据源序号
	 */
	public $dsnNumber = 0;
	
	/**
	 * [分表]配置信息
	 */
	public $confPartition = null;
	
	/**
	 * [分表]对应的SQL
	 */
	public $sqlPartition = null;
	
	/**
	 * 构造函数
	 */
	public function __construct($fieldId = 0)
	{
		$table = $this->table;
		$conf = App::instance()->dbTable[$this->dsnNumber];
		
		/// 前缀
		isset($conf['prefix']) and $table = $conf['prefix'].$table;
		
		/// 后缀(分表)
		if ($this->sqlPartition && $fieldId) {
			if (empty($this->confPartition)) 
				$partition = $conf['partition'];
			else 
				$partition = $this->confPartition;
				
			$num = intval($partition['num']);
			switch (trim($partition['type'])) {
				case 'mod':
					$suffix = ($fieldId % $num) + 1;
					$table .= '_'.$suffix;
					break;
					
				default:
					$suffix = ceil($fieldId/$num);
					$table .= '_'.$suffix;
					break;
			}
		}
		
		///
		$this->table = $table;
		$this->safeTable = lpString::dbQuote($table);
		$this->where = new Condition();
		$this->_resetSelect();
	}
	
	/**
	 * 取得DBO
	 */
	private function _getDbo()
	{
		if (!isset($this->_dbo)) 
			$this->_dbo = App::getMysql($this->dsnNumber);
			
		/// 分表状态
		if (!$this->_checkTable && $this->sqlPartition) {
			$this->_checkTable = true;
			$exists = $this->_dbo->existsTable($this->table);
			if (!$exists) {
				$sql = sprintf($this->sqlPartition, $this->table);
				$this->_dbo->operate($sql);
			}
		}
		
		return $this->_dbo;
	}
	
	/**
	 * 初始化查询
	 */
	private function _resetSelect()
	{
		$this->parts = self::$__selectPartsInit;
		$this->parts['FROM'][] = $this->safeTable;
		$this->where->clean();
	}
	
	/**
	 * 构造查询条件
	 */
	private function _selectWhere()
	{
		return $this->where->__toString();
	}
	
	/**
	 * 构造重复设置
	 */
	private function _selectDistinct($distinct)
	{
		return empty($distinct) ? '' : 'DISTINCT';
	}
	
	/**
	 * 构造聚合
	 */
	private function _selectAggregation($aggregation)
	{
		return empty($aggregation) ? '' : $aggregation;
	}
	
	/**
	 * 构造查询表
	 */
	private function _selectFrom($from)
	{
		return 'FROM '.(empty($from) ? $this->safeTable : implode(' ', $from));
	}
	
	/**
	 * 构造查询区间
	 */
	private function _selectLimit($limit)
	{
		if (empty($limit)) 
			return '';
			
		///
		list($start, $offset) = $limit;
		return "LIMIT {$start},{$offset}";
	}
	
	/**
	 * 构造查询字段
	 */
	private function _selectField($fields)
	{
		/*
		// 数据结构如下：
		array(
			'`talbe`.id',
			'username,password',
			'position as p,count(`id`) as `count`',
		)
		*/
		
		if (!empty($this->parts['AGGREGATION'])) 
			return '';
			
		if (empty($fields)) 
			return '*';
			
		///
		$p1 = "/^(`?(\w+)`?\.)?`?(\w+)`?( AS `?(\w+)`?)?$/i"; // "`table`.`position` as p"
		$p2 = "/^((\w+)\(`?\w+`?\))( AS `?(\w+)`?)?$/i"; // "count(`id`) as `count`"
		$columns = array();
		foreach ($fields as $f) {
			foreach (explode(',', $f) as $field) {
				$field = trim($field);
				if (preg_match($p1, $field, $m)) {
					$table = $m[2] ? lpString::dbQuote($m[2]) : $this->safeTable;
					$field = $table.'.'.lpString::dbQuote($m[3]);
					if ($m[5]) 
						$field .= ' AS '.lpString::dbQuote($m[5]);
						
					$columns[] = $field;
				} elseif (preg_match($p2, $field, $m)) {
					$field = $m[1];
					if ($m[4]) 
						$field .= ' AS '.lpString::dbQuote($m[4]);
						
					$columns[] = $field;
				}
			}
		}
		
		return implode(', ', $columns);
	}
	
	/**
	 * 构造查询排序
	 */
	private function _selectOrder($orders)
	{
		/*
		// 数据结构如下：
		array(
			'id desc',
			'username',
			'`table`.`id` desc',
		)
		*/
		
		if (empty($orders)) 
			return '';
			
		///
		$p = "/^(`?(\w+)`?\.)?`?(\w+)`?( (asc|desc))?$/i";
		foreach ($orders as $k => $field) {
			if (preg_match($p, $field, $m)) {
				$sort = $m[5] ? strtoupper($m[5]) : "ASC";
				$table = $m[2] ? lpString::dbQuote($m[2]) : $this->safeTable;
				$orders[$k] = $table.'.'.lpString::dbQuote($m[3]).' '.$sort;
			}
		}
		
		return "ORDER BY ".implode(',', $orders);
	}
	
	/**
	 * 构造查询聚合
	 */
	private function _selectGroup($group)
	{
		/*
		// 数据结构如下：
		`table`.`id`
		*/
		
		$p = "/^(`?(\w+)`?\.)?`?(\w+)`?$/i";
		if (preg_match($p, $group, $m)) {
			$table = $m[2] ? lpString::dbQuote($m[2]) : $this->safeTable;
			$group = $table.'.'.lpString::dbQuote($m[3]);
			return "GROUP BY ".$group;
		}
		
		return '';
	}
	
	/**
	 * 拼装SQL查询语句
	 */
	private function _querySelect()
	{
		$query = 'SELECT';
		foreach ($this->parts as $key => $value) {
			$method = '_select'.ucfirst($key);
			if (method_exists($this, $method)) {
				$tmp = call_user_func_array(array($this, $method), array($value));
				empty($tmp) or $query .= ' '.$tmp;
			}
		}
		
		///
		$this->_resetSelect();
		return $query;
	}
	
	/**
	 * 操作数据表
	 * 
	 * 执行操作后，清空WHERE条件
	 */
	private function _queryOperate()
	{
		$this->query .= $this->where;
		$this->where->clean();
		
		///
		return $this->execute();
	}
	
	/**
	 * 检测SQL执行后是否有错误输出
	 */
	private function _checkSql()
	{
		if ($this->_getDbo()->error) {
			array_push($this->errors, $this->_getDbo()->error);
			return false;
		}
		
		return true;
	}
	
	/**
	 * 聚合查询
	 */
	private function _getAggregation($type, $field)
	{
		$field = empty($field) ? '*' : lpString::dbQuote($field);
		
		///
		$this->parts['FIELD'] = null;
		$this->parts['AGGREGATION'] = "{$type}({$field})";
		return $this->getVar();
	}
	
	/**
	 * 表连接
	 
	private function _joinTable($type, $table, $field, $onWhere)
	{
		if (!empty($field)) {
			foreach (explode(',', $field) as $var) 
				$this->field($table.'.'.$var);
		}
		
		///
		$join = $type.' JOIN '.$table.' ON '.$onWhere;
		array_push($this->parts['FROM'], $join);
		return $this;
	}*/
	
	/**
	 * 设置操作条件
	 */
	public function where()
	{
		$args = func_get_args();
		$this->where->add($args, true);
		return $this;
	}
	
	/**
	 * 设置操作条件
	 */
	public function orWhere()
	{
		$args = func_get_args();
		$this->where->add($args, false);
		return $this;
	}
	
	/**
	 * 删除数据
	 * 
	 * 需要先调用where再调用此操作进行删除
	 */
	public function delete()
	{
		$format = "DELETE FROM %s";
		$this->query = sprintf($format, 
			$this->safeTable);
			
		///
		return $this->_queryOperate();
	}
	
	/**
	 * 插入数据
	 */
	public function insert($data)
	{
		$format = "INSERT INTO %s (%s) VALUES (%s)";
		$data = lpString::sqlEscape($data, true);
		$fields = array_keys($data);
		
		///
		$fields = lpString::dbQuote($fields);
		$this->query = sprintf($format, 
			$this->safeTable,
			implode(',', $fields),
			implode(',', $data));
		
		///
		$this->where->clean();
		return $this->_queryOperate();
	}
	
	/**
	 * 更新数据
	 * 
	 * 需要先调用where再调用此操作进行更新
	 */
	public function update($data)
	{
		$format = "UPDATE %s SET %s";
		$data = lpString::sqlEscape($data, true);
		$fields = array_keys($data);
		
		///
		$bits = array();
		foreach ($fields as $f) 
			$bits[] = lpString::dbQuote($f).'='.$data[$f];
			
		$this->query = sprintf($format, 
			$this->safeTable,
			implode(',', $bits));
			
		///
		return $this->_queryOperate();
	}
	
	/**
	 * 更新某个字段
	 */
	public function updateStep($field, $plus = true, $step = 1)
	{
		$format = "UPDATE %s SET %s=%s%s%d";
		$field = lpString::dbQuote($field);
		
		///
		$this->query = sprintf($format, 
			$this->safeTable,
			$field, $field,
			$plus ? '+' : '-',
			$step);
			
		return $this->_queryOperate();
	}
	
	/**
	 * 执行数据操作SQL
	 */
	function execute($query = null)
	{
		$query or $query = $this->query;
		$rowsAffected = $this->_getDbo()->query($query);
		if (!$this->_checkSql()) 
			return false;
		
		///
		$pattern = "/^(insert|update|delete)/i";
		$type = preg_match($pattern, $query, $m) ? strtolower($m[1]) : null;
		if ($type == 'insert' && $this->_getDbo()->insertId) 
			return $this->_getDbo()->insertId;
			
		return $rowsAffected ? $rowsAffected : true;
	}
	
	/**
	 * 设置重复
	 */
	public function distinct()
	{
		$this->parts['DISTINCT'] = true;
		return $this;
	}
	
	/**
	 * 设置聚合
	 */
	public function group($field)
	{
		$this->parts['GROUP'] = $field;
		return $this;
	}
	
	/**
	 * 设置查询区间
	 */
	public function limit($start, $offset)
	{
		$this->parts['LIMIT'] = array($start, $offset);
		return $this;
	}
	
	/**
	 * 设置查询字段
	 */
	public function field($field)
	{
		array_push($this->parts['FIELD'], $field);
		return $this;
	}
	
	/**
	 * 设置查询排序
	 * 
	 * 具体数据结构参看输出
	 */
	public function order($field)
	{
		array_push($this->parts['ORDER'], $field);
		return $this;
	}
	
	/**
	 * 内连接
	 
	public function innerJoin($table, $field, $onWhere)
	{
		return $this->_joinTable('INNER', $table, $field, $onWhere);
	}*/
	
	/**
	 * 右连接
	 
	public function leftJoin($table, $field, $onWhere)
	{
		return $this->_joinTable('LEFT', $table, $field, $onWhere);
	}*/
	
	/**
	 * 右连接
	 
	public function rightJoin($table, $field, $onWhere)
	{
		return $this->_joinTable('RIGHT', $table, $field, $onWhere);
	}*/
	
	/**
	 * 聚合查询 - 取总数
	 */
	public function getCount($field = '')
	{
		return $this->_getAggregation('COUNT', $field);
	}
	
	/**
	 * 聚合查询 - 取最大值
	 */
	public function getMax($field = '')
	{
		return $this->_getAggregation('MAX', $field);
	}
	
	/**
	 * 聚合查询 - 取最小值
	 */
	public function getMin($field = '')
	{
		return $this->_getAggregation('MIN', $field);
	}
	
	/**
	 * 聚合查询 - 取平均值
	 */
	public function getAvg($field = '')
	{
		return $this->_getAggregation('AVG', $field);
	}
	
	/**
	 * 聚合查询 - 取和
	 */
	public function getSum($field = '')
	{
		return $this->_getAggregation('SUM', $field);
	}
	
	/**
	 * 取得单行列记录值
	 */
	public function getVar($query = null)
	{
		$query or $query = $this->_querySelect();
		$var = $this->_getDbo()->getVar($query);
		if ($this->_checkSql() && $var) 
			return $var;
			
		return false;
	}
	
	/**
	 * 取得某列记录
	 */
	public function getCol($query = null)
	{
		$query or $query = $this->_querySelect();
		$a = $this->_getDbo()->getCol($query);
		if ($this->_checkSql() && $a) 
			return $a;
		
		return array();
	}
	
	/**
	 * 取得单行记录
	 */
	public function getRow($query = null)
	{
		$this->limit(0, 1);
		$query or $query = $this->_querySelect();
		$a = $this->_getDbo()->getRow($query);
		if ($this->_checkSql() && $a) 
			return $a;
		
		return array();
	}
	
	/**
	 * 取得多行记录
	 */
	public function getResults($query = null)
	{
		$query or $query = $this->_querySelect();
		$a = $this->_getDbo()->getResults($query);
		if ($this->_checkSql() && $a) 
			return $a;
		
		return array();
	}
	
	/**
	 * 取得模型对应的表名
	 */
	public function getTable()
	{
		return $this->table;
	}
}


/**
 * 模型数据操作或查询条件类
 */
class Condition
{
	/**
	 * @var 原始数据
	 */
	public $data = array();
	
	/**
	 * @var 条件数组
	 */
	public $conditions = array();
	
	/**
	 * @var false,字符串；true，数组
	 */
	public $type = false;
	
	/**
	 * 清空条件
	 */
	public function clean()
	{
		$this->data = array();
	}
	
	/**
	 * 添加条件
	 * 
	 * 一个条件组中，只能有一个解析方式
	 */
	public function add($where, $isAnd = true)
	{
		if (count($where) == 1 && is_array($where[0])) {
			$where = array_shift($where);
			$type = true;
		} else 
			$type = false;
		
		///
		$type == $this->type or $this->clean();
		$this->type = $type;
		
		///
		$this->data[] = array('where'=>$where, 'isAnd'=>$isAnd);
	}
	
	/**
	 * 打印出条件
	 */
	public function __toString()
	{
		if (empty($this->data)) 
			return '';
			
		$this->conditions = array();
		$function = $this->type ? '_whereArray' : '_whereString';
		foreach ($this->data as $var) {
			$where = $var['where'];
			if (count($where) == 0) 
				continue;
				
			///
			$params = array($where, $var['isAnd']);
			call_user_func_array(array($this, $function), $params);
		}
		
		return ' WHERE 1'.implode(' ', $this->conditions);
	}
	
	/**
	 * 字符串连接可以使用(?)来替换或直接写出条件，使用方式如下：
	 * 
	 * where('username="linxs"')
	 * where('username=?','linxs')
	 * where('title like "%?%"', 'keyword')
	 * where('id in(?)', '1,2,3')
	 */
	private function _whereString($where, $isAnd)
	{
		$format = explode('?', $where[0]);
		
		///
		$i = 1;
		$sql = $format[0];
		$pLike = "/LIKE [\'|\"](\%)?$/i";
		$pIn = "/IN\s?\($/i";
		
		///
		array_shift($where);
		foreach ($where as $var) {
			if (!isset($format[$i]) || is_array($var)) 
				break;
			
			if (preg_match($pLike, $sql) || preg_match($pIn, $sql)) 
				$sql .= $var;
			else
				$sql .= lpString::sqlEscape($var);
			
			$sql .= $format[$i];
			$i++;
		}
		
		empty($sql) or $this->conditions[] = $this->_join($isAnd).$sql;
	}
	
	/**
	 * 数组配置条件，使用方式：
	 * 
	 * where(array('id'=>2,'uid'=>1))
	 * where(array('id'=>array('in', array(1,2,3))))
	 * where(array('title'=>array('like', '%keyword%')))
	 */
	private function _whereArray($where, $isAnd)
	{
		$i = 0;
		$sql = '';
		$count = count($where);
		foreach ($where as $field => $var) {
			if ($i > 0 && $i < $count) 
				$sql .= ' AND ';
				
			$i++;
			$field = lpString::dbQuote($field);
			if (is_array($var)) {
				$type = array_shift($var);
				switch (strtoupper($type)) {
					///
					case 'IN':
						$data = lpString::sqlEscape($var[0]);
						if (is_array($data)) 
							$sql .= $field.' IN('.implode(',', $data).')';
							
						break;
						
					///
					case 'LIKE':
						$data = trim($var[0]);
						$sql .= $field.' LIKE'.$data;
						break;
				}
				
			} else 
				$sql .= $field.'='.lpString::sqlEscape($var);
		}
		
		empty($sql) or $this->conditions[] = $this->_join($isAnd).$sql;
	}
	
	/**
	 * 连接关系
	 */
	private function _join($isAnd)
	{
		return $isAnd === true ? ' AND ' : ' OR ';
	}
}
